/*
 * Proto Streamer
 * Copyright (C) 2009 John Pritchard.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 */
package p.s;

import java.io.IOException;
import java.io.PrintStream;

/**
 * Extend this class or implement {@link Response} to create
 * applications.
 * 
 * <h3>Lifecycle</h3>
 * 
 * The 'init' method defines the response.  The 'tail' method performs
 * any I/O related to the response.  Subclasses must define a response
 * status and must call 'super.init' from an overriding method named
 * 'init'.
 * 
 * <h3>Socket timeout</h3>
 * 
 * Many subclasses will want to change the socket timeout from the
 * default which is too short for streaming (protobuf) applications.
 * Streaming applications may want no socket timeout.  See {@link
 * #disableSocketTimeout(Request)}.
 * 
 * <h3>Keep alive</h3>
 * 
 * Subclasses responding with status codes in the 300 to 500 range
 * should not enable connection keep alive.  On the contrary, many
 * browsers depend on the server closing the connection on "not ok"
 * responses.  See {@link setConnection(Request)}.  See {@link
 * File$Get#init(Request)}.
 * 
 * @see Buffer
 * @see Error
 * @see File
 * @author jdp
 */
public class Abstract 
    extends Object
    implements Response
{


    protected Protocol protocol = Protocol.HTTP10;
    protected int statusCode, statusState;
    protected String statusMessage, statusLine;
    protected Headers headers;


    public Abstract(){
        super();
        this.headers = new Headers();
        this.headers.add(Date.Header);
        this.headers.add(Server.Header);
        this.headers.setConnectionClose();
    }


    public Response clone(){
        try {
            Abstract clone = (Abstract)super.clone();
            clone.headers = clone.headers.clone();
            return clone;
        }
        catch (CloneNotSupportedException exc){
            throw new java.lang.InternalError(exc.toString());
        }
    }
    public void init(Request request)
        throws IOException
    {
        this.headers.setDate();
        headers.add("X-PS+ID",request.requestId);
    }
    public final void head(Output out)
        throws IOException
    {
        out.println(this.getStatusLine());
        this.headers.write(out);
    }
    public void tail(Request request, Output out)
        throws IOException
    {
    }
    /**
     * Copy head for logging.
     */
    public final void head(PrintStream out){
        out.println(this.getStatusLine());
        this.headers.write(out);
    }
    public void disableSocketTimeout(Request request)
        throws IOException
    {
        request.disableSocketTimeout();
    }
    public final boolean isValid(){
        return (null != this.protocol && 0 != this.statusCode && null != this.statusMessage);
    }
    public final String getStatusLine(){
        String statusLine = this.statusLine;
        if (null == statusLine || this.statusCode != this.statusState){
            statusLine = (this.protocol.string+' '+this.statusCode+' '+this.statusMessage);
            this.statusLine = statusLine;
            this.statusState = this.statusCode;
        }
        return statusLine;
    }
    public final boolean isOk(){
        return (Status.Code.Ok == this.statusCode || Status.Code.Created == this.statusCode);
    }
    public final boolean isNotOk(){
        return (Status.Code.Ok != this.statusCode && Status.Code.Created != this.statusCode);
    }
    public final boolean isCreated(){
        return (Status.Code.Created == this.statusCode);
    }
    public final boolean isNotCreated(){
        return (Status.Code.Created != this.statusCode);
    }
    public final void redirect(Request request, String name){
        this.statusCode = Response.Status.Code.Found;
        this.statusMessage = "Found";
        this.headers.set("Location",request.getLocation(name));
    }
    public final void redirect(Request request, Welcome file){
        this.statusCode = Response.Status.Code.Found;
        this.statusMessage = "Found";
        this.setLocation(file);
    }
    public final void welcome(Request request){
        this.redirect(request,request.getLocationWelcome());
    }
    public final String getLocation(){
        return this.headers.valueOf("Location");
    }    
    public final void setLocation(Welcome file){
        this.headers.set("Location",file.location);
    }    
    public final void setLocation(Request request){
        this.headers.set("Location",request.getLocation());
    }    
    public final String getContentType(){
        return this.headers.valueOf("Content-Type");
    }    
    public final void setContentType(String value){
        this.headers.set("Content-Type",value);
    }
    public final String getContentLength(){
        return this.headers.valueOf("Content-Length");
    }
    public final void setContentLength(int value){
        this.headers.set("Content-Length",String.valueOf(value));
    }    
    public final void setStatusOk(){
        this.statusCode = Response.Status.Code.Ok;
        this.statusMessage = "Ok";
    }
    public final void setStatusCreated(){
        this.statusCode = Response.Status.Code.Created;
        this.statusMessage = "Created";
    }
    public final void setStatusAccepted(){
        this.statusCode = Response.Status.Code.Accepted;
        this.statusMessage = "Accepted";
    }
    public final void setStatusNoContent(){
        this.statusCode = Response.Status.Code.NoContent;
        this.statusMessage = "No Content";
    }
    public final void setStatusNotModified(){
        this.statusCode = Response.Status.Code.NotModified;
        this.statusMessage = "Not modified";
    }
    public final void setStatusBadRequest(){
        this.statusCode = Response.Status.Code.BadRequest;
        this.statusMessage = "Bad request";
    }
    public final void setStatusNotAuthorized(){
        this.statusCode = Response.Status.Code.NotAuthorized;
        this.statusMessage = "Not authorized";
    }
    public final void setStatusForbidden(){
        this.statusCode = Response.Status.Code.Forbidden;
        this.statusMessage = "Forbidden";
    }
    public final void setStatusNotFound(){
        this.statusCode = Response.Status.Code.NotFound;
        this.statusMessage = "Not found";
    }
    public final void setStatusLengthRequired(){
        this.statusCode = Response.Status.Code.LengthRequired;
        this.statusMessage = "Length Required";
    }
    public final void setStatusError(){
        this.statusCode = Response.Status.Code.Error;
        this.statusMessage = "Error";
    }
    public final void setStatusNotImplemented(){
        this.statusCode = Response.Status.Code.NotImplemented;
        this.statusMessage = "Not Implemented";
    }
    public final void setStatusHttpVersion(){
        this.statusCode = Response.Status.Code.HttpVersion;
        this.statusMessage = "Http Version";
    }
    public String getConnection(){
        return this.headers.getConnection();
    }
    /**
     * Set status and then call this when it may be good to employ
     * HTTP keep alive connections.
     */
    public void setConnection(Request request){
        if (this.isOk()){
            if (request.isProtocolHTTP11()){
                if (request.isConnectionKeepAlive()){
                    this.protocol = Protocol.HTTP11;
                    this.setConnectionKeepAlive();
                    return;
                }
            }
        }
        this.setConnectionClose();
    }
    public void setConnectionKeepAlive(){
        this.headers.setConnectionKeepAlive();
    }
    public boolean isConnectionKeepAlive(){
        return this.headers.isConnectionKeepAlive();
    }
    public boolean isNotConnectionKeepAlive(){
        return this.headers.isNotConnectionKeepAlive();
    }
    public void setConnectionClose(){
        this.headers.setConnectionClose();
    }
    public boolean isConnectionClose(){
        return this.headers.isConnectionClose();
    }
    public boolean isNotConnectionClose(){
        return this.headers.isNotConnectionClose();
    }

}
